#!/usr/bin/python

import os
import sqlite3

class Commit (object):
    commits = {}

    @classmethod
    def get(klass, sha1):
        if sha1 not in klass.commits:
            klass.commits[sha1] = Commit(sha1)
        return klass.commits[sha1]
            
    def __init__(self, sha1):
        self.sha1 = sha1
        self.parents = []
        self.children = []
        self.branches = []
        self.branch = ""

        parents, self.date = get_commit_info(sha1)
        for parent in parents:
            self.add_parent(parent)

    def add_parent(self, parent):
        if not isinstance(parent, Commit):
            parent = Commit.get(parent)

        if parent in self.parents:
            return

        self.parents.append(parent)
        self.parents[-1].add_child(self)

    def add_child(self, child):
        if not isinstance(child, Commit):
            child = Commit.get(child)

        if child in self.children:
            return

        self.children.append(child)
        self.children[-1].add_parent(self)

    def add_branch(self, branch):
        if branch in self.branches:
            return

        self.branches.append(branch)

    def __str__(self):
        p = ""
        c = ""
        b = ""
        for parent in self.parents:
            p += "\n     p:%s" % parent.sha1
        for child in self.children:
            c += "\n     c:%s" % child.sha1
        for branch in self.branches:
            b += "\n     b:%s" % branch
        return "Commit(%s %s\n       %s%s%s%s)\n" % (self.date, self.branch,
                                                     self.sha1, p, c, b)


class Tag (object):
    tags = {}

    @classmethod
    def get(klass, name):
        if name not in klass.tags:
            return None
        return klass.tags[name]

    @classmethod
    def create(klass, name, sha1):
        if name not in klass.tags:
            klass.tags[name] = Tag(name, sha1)
        return klass.tags[name]

    def __init__(self, name, sha1):
        self.name = name
        self.tag = ""
        self.commit = ""
        self.date = None
        otype = get_object_type(sha1)
        if otype == 'commit':
            self.commit = sha1
            parents, date = get_commit_info(self.commit)
            self.date = date
        elif otype == 'tag':
            self.tag = sha1
            sha1, date = get_tag_info(self.tag)
            self.date = date
            if get_object_type(sha1) == 'commit':
                self.commit = sha1

    def __str__(self):
        t = ""
        c = ""
        if self.tag is not None:
            t += "\n  t:%s" % self.tag
        if self.commit is not None:
            c += "\n  c:%s" % self.commit
        return "Tag(%s %s%s%s)\n" % (self.name, self.date, t, c)


def get_object_type(sha1):
    object_type = ""

    _in, out, err = os.popen3('git cat-file -t %s' % sha1)

    object_type = out.read().strip()

    _in.close()
    out.close()
    err.close()

    return object_type


def get_tag_info(sha1):
    tagged = None
    date = None

    _in, out, err = os.popen3('git cat-file tag %s' % sha1)

    for line in out:
        if line.startswith('object'):
            tagged = line[6:].strip()
        elif line.startswith('tagger'):
            date = line[-16:-6].strip()

    _in.close()
    out.close()
    err.close()

    return tagged, date


def get_commit_info(sha1):
    parents = []
    date = ""

    _in, out, err = os.popen3('git cat-file commit %s' % sha1)

    for line in out:
        if line.startswith('parent'):
            parents.append(line[6:].strip())
        elif line.startswith('committer'):
            date = line[-16:-6].strip()

    _in.close()
    out.close()
    err.close()

    return parents, date


def get_rev_list(what):
    sha1s = []

    _in, out, err = os.popen3('git rev-list --date-order --reverse %s' % what)

    for line in out:
        sha1s.append(line.strip())

    _in.close()
    out.close()
    err.close()

    return sha1s


def get_commits():
    commits = []

    for sha1 in get_rev_list('--all'):
        commits.append(Commit.get(sha1))

    return commits


def get_tags():
    tags = {}

    _in, out, err = os.popen3('git show-ref --tags')

    for line in out:
        sha1, name = [x.strip() for x in line.split()]
        tag = Tag.create(name, sha1)
        tags.setdefault(tag.date, []).append(tag)

    _in.close()
    out.close()
    err.close()

    tag_list = []
    for date in sorted(tags.keys()):
        for tag in tags[date]:
            tag_list.append(tag)

    return tag_list


def get_branch_commits():
    branches = {}

    _in, out, err = os.popen3('git show-ref --heads')

    for line in out:
        sha1, name = [x.strip() for x in line.split()]
        branches[name] = get_rev_list(name)

    _in.close()
    out.close()
    err.close()

    return branches


def register_transaction(action, ref, sha1, origin=""):
    if len(sha1) == 0:
        cursor.execute('INSERT INTO "transactions" (action, ref, origin) VALUES (?, ?, ?);', (action, ref, origin))
    else:
        cursor.execute('INSERT INTO "transactions" (action, ref, sha1, origin) VALUES (?, ?, ?, ?);', (action, ref, sha1, origin))


def main():
    global cursor

    conn = sqlite3.connect('svnserver/db', isolation_level='IMMEDIATE')
    cursor = conn.cursor()
    
    commits = get_commits()
    branches = get_branch_commits()
    tags = get_tags()

    origins = {}

    print len(commits)
    for commit in commits:
        for branch, sha1s in branches.items():
            if commit.sha1 in sha1s:
                commit.add_branch(branch)

    for commit in commits:
        if len(commit.branches) == 1:
            commit.branch = commit.branches[0]
        elif len(commit.parents) == 0 and 'refs/heads/master' in commit.branches:
            commit.branch = 'refs/heads/master'
            origins[commit.branch] = commit.sha1

        if len(commit.children) == 1 and len(commit.branch) > 0:
            commit.children[0].branch = commit.branch

    for commit in commits:
        if len(commit.children) > 1:
            unknown_children = {}
            for child in commit.children:
                if len(child.branch) == 0:
                    b = tuple(sorted(child.branches))
                    unknown_children.setdefault(b, [])
                    unknown_children[b].append(child)
            if len(unknown_children) == 0:
                continue

    branch_points = []
    for commit in commits:
        if len(commit.children) > 1:
            used = []
            for child in commit.children:
                if len(child.branch) > 0:
                    used.append(child.branch)
            if len(commit.branch) > 0:
                if commit.branch not in used:
                    for child in commit.children:
                        if len(child.branch) > 0:
                            continue
                        child.branch = commit.branch
                        break
                    used.append(commit.branch)
            for child in commit.children:
                if len(child.branch) == 0:
                    longest = (0, None)
                    for branch in child.branches:
                        if branch in used:
                            continue
                        l = len(branches[branch])
                        if l > longest[0]:
                            longest = (l, branch)
                    if longest[1] is not None:
                        child.branch = longest[1]
                        used.append(longest[1])
                if child.branch not in origins:
                    origins[child.branch] = child.sha1
            branch_points.append(commit.sha1)

##    merge_points = []
##    for commit in commits:
##        if len(commit.parents) > 1:
##            print "merge point", commit.sha1, commit.branch
##            for parent in commit.parents:
##                print "  ", parent.branches, parent.branch
##            merge_points.append(commit.sha1)

    for commit in commits:
        if len(commit.children) == 1 and len(commit.branch) > 0:
            commit.children[0].branch = commit.branch

    seen = set()
    for commit in commits:
        while tags and tags[0].date < commit.date:
            tag = tags.pop(0)
            print "   tag '%s' '%s' '%s'" % (tag.name, tag.tag[:8], tag.commit[:8])
            register_transaction('tag', tag.name, tag.tag, tag.commit)
        if len(commit.parents) > 1:
            print " merge '%s' '%s' '%s'" % (commit.branch, commit.sha1[:8], " ".join([x.sha1[:8] for x in commit.parents]))
            print "       %s" % (" ".join([x.branch for x in commit.parents]))
            register_transaction('merge', commit.branch, commit.sha1,
                                 " ".join([x.sha1 for x in commit.parents]))
        elif len(commit.parents) > 0 and commit.branch != commit.parents[0].branch and commit.branch not in seen:
            print "branch '%s' '%s' '%s'" % (commit.branch, commit.sha1[:8], commit.parents[0].sha1[:8])
            print "       %s" % (commit.parents[0].branch)
            register_transaction('branch', commit.branch, commit.sha1,
                                 commit.parents[0].sha1)
        else:
            print "commit '%s' '%s'" % (commit.branch, commit.sha1[:8])
            register_transaction('commit', commit.branch, commit.sha1)
        seen.add(commit.branch)

    while tags:
        tag = tags.pop(0)
        print "   tag '%s' '%s' '%s'" % (tag.name, tag.tag[:8], tag.commit[:8])
        register_transaction('tag', tag.name, tag.tag, tag.commit)

    cursor.close()
    conn.commit()
    conn.close()


if __name__ == "__main__":
    main()
